import Vue,{PropType,VNode,VueConstructor} from 'vue';
import {
	getFirstComponentChild,
	getComponentName,
	remove,
	isRegExp
} from '../../js/utils';
//region 定义接口
/**
 * 缓存
 */
type VNodeCache={[key:string]:VNode};
type Pattern=string|RegExp|Array<string>;
/**
 * 定义非响应数据
 */
export interface MyKeepAliveVue extends Vue{
	cache:VNodeCache,
	keys:Array<string|number>,
	_vnode:VNode,
}

/**
 * 表达式和名称是否匹配
 * @param {string|RegExp|Array<string} pattern	表达式
 * @param {string} name		名称
 * @return {boolean}		是否匹配
 */
function matches(pattern:Pattern,name:string):boolean{
	if(Array.isArray(pattern)){
		return pattern.indexOf(name)>-1;
	}else if(typeof pattern==='string'){
		return pattern.split(',').indexOf(name)>-1;
	}else if(isRegExp(pattern)){
		return pattern.test(name);
	}
	return false;
}
/**
 * 删除不匹配的缓存
 * @param keepAliveInstance	公共缓存组件
 * @param filter			检查匹配函数
 */
function pruneCache(keepAliveInstance:any,filter:(name:string) => boolean):void{
	const {cache,keys,_vnode}=keepAliveInstance;
	for(const key in cache){
		const cachedNode=cache[key];
		if(cachedNode){
			const name=getComponentName(cachedNode.componentOptions);
			if(name&&!filter(name)){
				pruneCacheEntry(cache,key,keys,_vnode);
			}
		}
	}
}
/**
 * 删除指定的key
 * @param cache		缓存
 * @param key		删除的key
 * @param keys		所有的keys
 * @param current	当前节点
 */
function pruneCacheEntry(cache:VNodeCache,key:string|number,keys:Array<string|number>,current?:VNode){
	const cached=cache[key];
	if(cached&&(!current||cached.tag!==current.tag)){
		cached.componentInstance!.$destroy();
	}
	delete cache[key];
	remove(keys,key);
}
/**
 * 缓存操作接口
 */
export interface CacheOperator{
	clear:() => void,
	splice:(index:number,deleteCount:number) => void,
	removeKey:(key:number|string) => void,
	remove:(index:number) => void,
	shift:() => void,
	pop:() => void,
}
const patternTypes=[String,RegExp,Array] as PropType<Pattern>;
//endregion
/**
 * 缓存组件
 * @author 唐国雄
 * @date 2021/8/7
 * 属性----------------------
 * :include			必须有组件名且匹配会被缓存
 * :exclude			匹配的不会被缓存,
 * 方法----------------------
 * clear()										清空缓存
 * splice(index:number,deleteCount:number)		批量删除缓存
 * removeKey(key:number|string)					删除指定键缓存
 * remove(index:number)							删除指定索引缓存
 * shift()										删除第一个缓存
 * pop()										删除最后一个缓存
 * 事件----------------------
 * @cache-operator		把所有方法发射出去
 */
export default (Vue as VueConstructor<MyKeepAliveVue>).extend({
	name:'MyKeepAlive',
	...{
		abstract:true,
	},
	// @ts-ignore
	props:{
		include:patternTypes,
		exclude:patternTypes,
	},
	created(){
		this.cache=Object.create(null);
		this.keys=[];
		this.$emit('cache-operator',{
			clear:this.clear,
			splice:this.splice,
			removeKey:this.removeKey,
			remove:this.remove,
			shift:this.shift,
			pop:this.pop,
		} as CacheOperator);
	},
	mounted(){
		this.$watch('include',(val) => {
			pruneCache(this,(name) => matches(val,name));
		});
		this.$watch('exclude',(val) => {
			pruneCache(this,(name) => !matches(val,name));
		});
	},
	destroyed(){
		for(const key in this.cache){
			pruneCacheEntry(this.cache,key,this.keys);
		}
	},
	methods:{
		//删除全部缓存
		clear(){
			for(const key in this.cache){
				pruneCacheEntry(this.cache,key,this.keys);
			}
		},
		//批量删除
		splice(index:number,deleteCount:number){
			const {cache,keys}=this;
			const arr=keys.splice(index,deleteCount);
			for(let i=0;i<arr.length;++i){
				pruneCacheEntry(cache,arr[i],keys);
			}
		},
		//删除指定键缓存
		removeKey(key:number|string){
			this.remove(this.keys.indexOf(key));
		},
		//删除指定索引缓存
		remove(index:number){
			const {cache,keys}=this;
			if(index>=0&&index<keys.length){
				pruneCacheEntry(cache,keys[index],keys);
			}
		},
		shift(){
			const {cache,keys}=this;
			const key=keys.shift();
			if(key!==undefined){
				pruneCacheEntry(cache,key,keys);
			}
		},
		pop(){
			const {cache,keys}=this;
			const key=keys.pop();
			if(key!==undefined){
				pruneCacheEntry(cache,key,keys);
			}
		},
	},
	render():VNode{
		const slot=this.$slots.default;
		const vnode=getFirstComponentChild(slot);
		const componentOptions=vnode&&vnode.componentOptions;
		if(vnode&&componentOptions){
			const name=getComponentName(componentOptions);
			const {include,exclude}=this;
			if(
				// not included
				(include&&(!name||!matches(include,name)))||
				// excluded
				(exclude&&name&&matches(exclude,name))
			){
				return vnode;
			}
			const {cache,keys}=this;
			const key=vnode.data&&vnode.data.key
				?vnode.data.key
				:vnode.key
					?(vnode.key as string)
					:(componentOptions.Ctor as any).cid+(componentOptions.tag?`::${componentOptions.tag}`:'');
			if(cache[key]){
				vnode.componentInstance=cache[key].componentInstance;
			}else{
				cache[key]=vnode;
				keys.push(key);
			}
			vnode.data!.keepAlive=true;
		}
		// @ts-ignore
		return vnode||(slot&&slot[0]);
	}
});
